package cn.iocoder.yudao.module.reading.service.plan;

import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.reading.controller.app.plan.vo.ReadingPlanCreateReqVO;
import cn.iocoder.yudao.module.reading.dal.dataobject.book.PictureBookDO;
import cn.iocoder.yudao.module.reading.dal.dataobject.plan.PlanBookSetDO;
import cn.iocoder.yudao.module.reading.dal.dataobject.plan.ReadingPlanDO;
import cn.iocoder.yudao.module.reading.dal.mysql.book.PictureBookMapper;
import cn.iocoder.yudao.module.reading.dal.mysql.plan.PlanBookSetMapper;
import cn.iocoder.yudao.module.reading.dal.mysql.plan.PlanDailyTaskMapper;
import cn.iocoder.yudao.module.reading.service.plan.PlanDailyTaskService;
import cn.iocoder.yudao.module.reading.dal.mysql.plan.ReadingPlanMapper;
import cn.iocoder.yudao.module.reading.enums.plan.PlanStatusEnum;
import cn.iocoder.yudao.module.reading.enums.plan.PlanTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import jakarta.annotation.Resource;
import java.time.LocalDate;
import java.util.List;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.reading.enums.ErrorCodeConstants.*;

/**
 * 阅读计划 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
@Slf4j
public class ReadingPlanServiceImpl implements ReadingPlanService {

    @Resource
    private ReadingPlanMapper readingPlanMapper;
    @Resource
    private PlanBookSetMapper planBookSetMapper;
    @Resource
    private PlanDailyTaskMapper planDailyTaskMapper;
    @Resource
    private PictureBookMapper pictureBookMapper;
    @Resource
    private PlanDailyTaskService planDailyTaskService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createReadingPlan(Long userId, ReadingPlanCreateReqVO createReqVO) {
        // 1. 校验计划名称是否重复（排除已取消的计划）
        ReadingPlanDO existingPlan = readingPlanMapper.selectActiveByUserIdAndName(userId, createReqVO.getPlanName());
        if (existingPlan != null) {
            throw exception(READING_PLAN_NAME_DUPLICATE);
        }

        // 2. 计算计划总绘本数
        List<Long> bookSetIds = createReqVO.getCollectionIds();
        List<PictureBookDO> allBooks = pictureBookMapper.selectByBookSetIds(bookSetIds);
        int totalBooks = allBooks.size();

        // 3. 计算预计完成天数
        int estimatedDays = calculateEstimatedDays(totalBooks, createReqVO.getDailyCount(), createReqVO.getReviewSchedule());

        // 4. 创建阅读计划
        ReadingPlanDO plan = ReadingPlanDO.builder()
                .userId(userId)
                .planName(createReqVO.getPlanName())
                .planType(createReqVO.getPlanType())
                .cover(createReqVO.getCover())
                .dailyCount(createReqVO.getDailyCount())
                .reviewSchedule(createReqVO.getReviewSchedule())
                .totalBooks(totalBooks)
                .estimatedDays(estimatedDays)
                .currentDay(1)
                .completedBooks(0)
                .status(PlanStatusEnum.ACTIVE.getCode())
                .startDate(LocalDate.now())
                .build();

        readingPlanMapper.insert(plan);

        // 5. 创建计划绘本集关联
        for (int i = 0; i < bookSetIds.size(); i++) {
            PlanBookSetDO planBookSet = PlanBookSetDO.builder()
                    .planId(plan.getId())
                    .bookSetId(bookSetIds.get(i))
                    .sortOrder(i + 1)
                    .build();
            planBookSetMapper.insert(planBookSet);
        }

        // 6. 生成初始任务
        try {
            planDailyTaskService.generateTasks(plan.getId());
            log.info("计划任务生成成功: planId={}", plan.getId());
        } catch (Exception e) {
            log.error("计划任务生成失败: planId={}", plan.getId(), e);
            // 任务生成失败不影响计划创建，可以后续手动生成
        }

        return plan.getId();
    }

    @Override
    public ReadingPlanDO getReadingPlan(Long id) {
        ReadingPlanDO plan = readingPlanMapper.selectById(id);
        if (plan == null) {
            throw exception(READING_PLAN_NOT_EXISTS);
        }
        return plan;
    }

    @Override
    public List<ReadingPlanDO> getReadingPlansByUserId(Long userId, String status) {
        return readingPlanMapper.selectByUserId(userId, status);
    }

    @Override
    public void updatePlanProgress(Long planId) {
        ReadingPlanDO plan = getReadingPlan(planId);

        // 统计已完成的唯一绘本数量（每本绘本只计算一次）
        Long completedBooks = planDailyTaskMapper.countCompletedBooksByPlanId(planId);

        // 获取已完成任务的最大天数，用于更新currentDay
        Integer maxCompletedDay = planDailyTaskMapper.getMaxCompletedDayNumber(planId);
        Integer newCurrentDay = maxCompletedDay > 0 ? maxCompletedDay + 1 : 1; // 当前天数 = 最大已完成天数 + 1

        // 更新计划进度
        ReadingPlanDO updateObj = ReadingPlanDO.builder()
                .id(planId)
                .completedBooks(completedBooks.intValue())
                .currentDay(newCurrentDay)
                .build();

        // 检查是否完成
        if (completedBooks >= plan.getTotalBooks()) {
            updateObj.setStatus(PlanStatusEnum.COMPLETED.getCode());
            updateObj.setEndDate(LocalDate.now());
        }

        readingPlanMapper.updateById(updateObj);
    }

    @Override
    public void pausePlan(Long planId) {
        updatePlanStatus(planId, PlanStatusEnum.PAUSED.getCode());
    }

    @Override
    public void resumePlan(Long planId) {
        updatePlanStatus(planId, PlanStatusEnum.ACTIVE.getCode());
    }

    @Override
    public void completePlan(Long planId) {
        ReadingPlanDO updateObj = ReadingPlanDO.builder()
                .id(planId)
                .status(PlanStatusEnum.COMPLETED.getCode())
                .endDate(LocalDate.now())
                .build();
        readingPlanMapper.updateById(updateObj);
    }

    @Override
    public void cancelPlan(Long planId) {
        updatePlanStatus(planId, PlanStatusEnum.CANCELLED.getCode());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deletePlan(Long planId) {
        // 校验计划是否存在
        ReadingPlanDO plan = getReadingPlan(planId);

        log.info("开始删除阅读计划: planId={}, planName={}", planId, plan.getPlanName());

        try {
            // 1. 删除计划的所有任务
            planDailyTaskService.deleteTasksByPlanId(planId);
            log.info("删除计划任务成功: planId={}", planId);

            // 2. 删除计划绘本集关联
            planBookSetMapper.delete(new LambdaQueryWrapperX<PlanBookSetDO>()
                    .eq(PlanBookSetDO::getPlanId, planId));
            log.info("删除计划绘本集关联成功: planId={}", planId);

            // 3. 删除计划本身
            readingPlanMapper.deleteById(planId);
            log.info("删除阅读计划成功: planId={}", planId);

        } catch (Exception e) {
            log.error("删除阅读计划失败: planId={}", planId, e);
            throw e; // 重新抛出异常，触发事务回滚
        }
    }

    /**
     * 更新计划状态
     */
    private void updatePlanStatus(Long planId, String status) {
        ReadingPlanDO updateObj = ReadingPlanDO.builder()
                .id(planId)
                .status(status)
                .build();
        readingPlanMapper.updateById(updateObj);
    }

    /**
     * 计算预计完成天数（包含新学+复习）
     */
    private int calculateEstimatedDays(int totalBooks, int dailyCount, List<String> reviewSchedule) {
        // 1. 计算新学习需要的天数
        int newLearningDays = (int) Math.ceil((double) totalBooks / dailyCount);

        // 2. 获取复习策略中的最大间隔天数
        int maxReviewOffset = getMaxReviewOffset(reviewSchedule);

        // 3. 总天数 = 新学习的最后一天 + 最大复习间隔天数
        return newLearningDays + maxReviewOffset;
    }

    /**
     * 获取复习策略中的最大间隔天数
     */
    private int getMaxReviewOffset(List<String> reviewSchedule) {
        if (reviewSchedule == null || reviewSchedule.isEmpty()) {
            return 0;
        }

        // 如果复习策略包含"0"，表示不复习
        if (reviewSchedule.contains("0")) {
            return 0;
        }

        int maxOffset = 0;
        for (String reviewDayStr : reviewSchedule) {
            try {
                int offset = Integer.parseInt(reviewDayStr);
                if (offset > 0) { // 只考虑大于0的间隔天数
                    maxOffset = Math.max(maxOffset, offset);
                }
            } catch (NumberFormatException e) {
                // 忽略无效的复习间隔
                continue;
            }
        }
        return maxOffset;
    }

}
